from CvPythonExtensions import *

import GerikSpells
from GerikSpells import GerikSpellBase
from GSGroupPolicies import GSAll as GSGroup
from GSHandlerPolicies import GSTargetting as GSHandler
from GSResultsPolicies import GSMission as GSResults

from GSTargetInfoPolicies import GSSinglePlotTarget
from GSTargetInfoPolicies import GSSpecificUnitTarget

from GSGraphicsPolicies import GSPlot
from GSGraphicsPolicies import GSPieMenu

import GerikSpellsUtils

import CivcraftSCCombat as Combat

gc = CyGlobalContext()

# A target policy that works like the normal SpecificUnitTarget, but just
# changes how the bars look.
class CivcraftSpecificUnitTarget(GSSpecificUnitTarget):
    def __init__(self, pTargettableCallback):
        GSSpecificUnitTarget.__init__(self, pTargettableCallback)

    def getBars(self):
        bShowHealth = False
        bShowShields = False
        bShowEnergy = False
        if (self.getTarget().isFighting()):
                bShowHealth = False
                bShowShields = False
                bShowEnergy = False
        else:
                bShowHealth = True
                bShowShields = self.getTarget().hasShields()
                bShowEnergy = self.getTarget().hasEnergy()

        pBars = []
        if bShowHealth:
            pBars.append( (self.getTarget().currHitPoints(), self.getTarget().maxHitPoints(), "COLOR_HIT_POINTS_TEXT") )

        if bShowShields:
            pBars.append( (self.getTarget().currShields(), self.getTarget().maxShields(), "COLOR_SHIELDS_TEXT") )

        if bShowEnergy:
            pBars.append( (self.getTarget().currEnergy(), self.getTarget().maxEnergy(), "COLOR_ENERGY_TEXT") )

        return pBars

class AttackBase(GerikSpellBase, GSGroup, GSHandler):
    def __init__(self):
        GerikSpellBase.__init__(self)
        GSGroup.__init__(self)
        GSHandler.__init__(self)
        self.m_iLurkerType = gc.getInfoTypeForString("UNIT_LURKER")

    def canUnitCast(self, pCastingUnit, pPlot, bVisible):
        # Lurkers have their own special attacks.
        if pCastingUnit.getUnitType() == self.m_iLurkerType:
            return False

        if not pCastingUnit.canSCAttack(pPlot):
            return False

        if bVisible:
            return True

        if not pCastingUnit.hasMoreAttacksLeft():
            return False

        return True
            

    def canTargetPlot(self, pCastingUnit, pPlot, pTargetPlot, bVisible):
        return True

    def createTargettingStageList(self, bAlt, bShift, bCtrl):
        pPlotTargetPolicy = GSSinglePlotTarget(self.canTargetPlot)
        pPlotTargetPolicy.setRangeMakerCallback(self.getRangePlotList)
        pPlotTargetPolicy.setCheckRange(True)

        pPlotGraphicsPolicy = GSPlot()
        pPlotGraphicsPolicy.setShowRangeGraphics(False)
        pPlotGraphicsPolicy.setCursorType("CURSOR_SINGLE_ATTACK")
        return [(pPlotTargetPolicy, pPlotGraphicsPolicy)]

    def getRangePlotList(self, pCastingPlot):
        return GerikSpellsUtils.getRangePlotDistanceList(pCastingPlot, self.getCastingGroup().getMaxRange() + 1)

class NormalAttackBase(AttackBase, GSResults):
    def __init__(self):
        AttackBase.__init__(self)
        GSResults.__init__(self, GSSinglePlotTarget())

    def startSpellResultsOnTarget(self, pCastingGroup, pTarget):
        CyEngine().triggerEffect(40, pTarget.getPoint())
        

class TargettedAttackBase(AttackBase, GSResults):
    def __init__(self):
        AttackBase.__init__(self)
        GSResults.__init__(self, CivcraftSpecificUnitTarget(self.canTargetUnit))

    def createTargettingStageList(self, bAlt, bShift, bCtrl):
        stageList = AttackBase.createTargettingStageList(self, bAlt, bShift, bCtrl)

        # Second stage. Get a unit.
        pUnitTargetPolicy = CivcraftSpecificUnitTarget(self.canTargetUnit)
        pUnitMenuPolicy = GSPieMenu()
        stageList.append( (pUnitTargetPolicy, pUnitMenuPolicy) )

        return stageList

    def canTargetUnit(self, pCastingGroup, pCastingPlot, pTargetUnit, bVisible):
        for pUnit in pCastingGroup.getUnits():
            if pUnit.canSCAttackUnit(pTargetUnit, False):
                return True    

# A graphics policy that will be used for targetting with Lurkers.
class LurkerPlotGraphicsPolicy(GSPlot):
    def __init__(self, pCastingGroup, pCastingPlot):
        GSPlot.__init__(self)
        self.m_pCastingGroup = pCastingGroup
        self.m_pCastingPlot = pCastingPlot
        self.m_iLurkerType = gc.getInfoTypeForString("UNIT_LURKER")

    def draw(self):
        self.undrawTargets()

        for pPlot in self.m_pTargetInfo.getSecondaryTargetList(self.m_pCastingGroup, self.m_pCastingPlot)[0]:
            pPlotValueTupleList = getLurkerAttackPlotValues(self.m_pCastingPlot, pPlot, gc.getUnitInfo(self.m_iLurkerType).getGroundRange())
            pColor = NiColorA()
            pColor.r = 1.0
            pColor.g = 0.0
            pColor.b = 0.0
            pColor.a = 0.0
            for pPlot, iPlotValue in pPlotValueTupleList:
                pColor.a = 0.0 + (float(iPlotValue) / 100.0)
                CyEngine().addColoredPlot(pPlot.getX(), pPlot.getY(), pColor, 2)
                #self.drawAffectedPlot(pPlot)

class LurkerAttackBase(AttackBase, GSResults):
    def __init__(self):
        AttackBase.__init__(self)
        GSResults.__init__(self, GSSinglePlotTarget())

    def canUnitCast(self, pCastingUnit, pPlot, bVisible):
        if pCastingUnit.getUnitType() != self.m_iLurkerType:
            return False

        if not pCastingUnit.canSCAttack(pPlot):
            return False

        if not pCastingUnit.isBurrowed():
            return False

        if bVisible:
            return True

        if not pCastingUnit.hasMoreAttacksLeft():
            return False

        return True

    def createTargettingStageList(self, bAlt, bShift, bCtrl):
        pPlotTargetPolicy = GSSinglePlotTarget(self.canTargetPlot)
        pPlotTargetPolicy.setRangeMakerCallback(self.getRangePlotList)
        pPlotTargetPolicy.setCheckRange(True)

        pPlotGraphicsPolicy = LurkerPlotGraphicsPolicy(self.getCastingGroup(), self.getCastingPlot())
        pPlotGraphicsPolicy.setShowRangeGraphics(False)
        pPlotGraphicsPolicy.setCursorType("CURSOR_SINGLE_ATTACK")
        return [(pPlotTargetPolicy, pPlotGraphicsPolicy)]

#
# Now the actual spell classes.
#


#
# Single Attack
#
class SingleAttack(NormalAttackBase):
    def __init__(self):
        NormalAttackBase.__init__(self)

    def startSpellResultsOnTarget(self, pCastingGroup, pTargetPlot):
        bCombat = False
        for pUnit in pCastingGroup.getUnits():
            if Combat.singleAttack(pUnit, pTargetPlot):
                bCombat = True

        if bCombat:
            CyEngine().triggerEffect(40, pTargetPlot.getPoint())

        return False

#
# Single Attack Targetted
#
class SingleAttackTargetted(TargettedAttackBase):
    def __init__(self):
        TargettedAttackBase.__init__(self)

    def startSpellResultsOnTarget(self, pCastingGroup, pTargetUnit):
        bCombat = False
        for pUnit in pCastingGroup.getUnits():
            if Combat.singleAttackTargetted(pUnit, pTargetUnit):
                bCombat = True
            if pTargetUnit.isDead():
                break

        if bCombat:
            CyEngine().triggerEffect(40, pTargetUnit.plot().getPoint())
        return False

#
# Turn Attack
#
class TurnAttack(NormalAttackBase):
    def __init__(self):
        NormalAttackBase.__init__(self)

    def startSpellResultsOnTarget(self, pCastingGroup, pTargetPlot):
        bCombat = False
        for pUnit in pCastingGroup.getUnits():
            if Combat.turnAttack(pUnit, pTargetPlot):
                bCombat = True

        if bCombat:
            CyEngine().triggerEffect(40, pTargetPlot.getPoint())

        return False

#
# Turn Attack Targetted
#
class TurnAttackTargetted(TargettedAttackBase):
    def __init__(self):
        TargettedAttackBase.__init__(self)

    def startSpellResultsOnTarget(self, pCastingGroup, pTargetUnit):
        bCombat = False
        for pUnit in pCastingGroup.getUnits():
            if Combat.turnAttackTargetted(pUnit, pTargetUnit):
                bCombat = True
            if pTargetUnit.isDead():
                break

        if bCombat:
            CyEngine().triggerEffect(40, pTargetUnit.plot().getPoint())
        return False

def isNotValid(x):
    return not x or x.isNone()

def createNewGroup(pParticipantUnits):
    if not pParticipantUnits:
        return None

    iLength = len(pParticipantUnits)
    if iLength == 0:
        return None
    
    pHeadUnit = pParticipantUnits[0]
    pHeadUnit.joinGroup(None, True, True)
    pNewGroup = pHeadUnit.getGroup()
    # Place rest of units (if any) that can't attack into this group.
    map(lambda x: x.joinGroup(pNewGroup, True, True), pParticipantUnits[1:])

    return pNewGroup

#
# Desroy Attack
#
#GERIKESTODO: This is a work in progress, so I'm making it not callable.
class DestroyAttack(NormalAttackBase):
    def __init__(self):
        NormalAttackBase.__init__(self)

    # GERIKESTODO: Remove this when ready to use.
    def canUnitCast(self, pCastingUnit, pCastingPlot, bVisible):
        return False


    def startSpellResultsOnTarget(self, pCastingGroup, pTargetPlot):
        """
        Gets all the units that cannot attack any unit in the plot, and puts them in a seperate group.
        Then, gets targets for each unit. A group is created that has all the units that are going to start
        destroy mode on that one target. Then, for each group, the group gets pushed the destroyAttackTargetted mission
        with that target.
        """
        # Don't do anything if there are no targets to pick from.
        if pTargetPlot.getNumVisibleEnemyDefenders(pCastingGroup.getOwner()) == 0:
            return False

        # List of units that will be seperated out, since there are no units that they can attack.
        pCannotAttackUnits = []

        # Dictionary of target (actually, their ID's) and the list of units that will be attacking them.
        pTargetAttackers = {}

        for pLoopUnit in pCastingGroup.getUnits():
            pTargetUnit = pTargetPlot.getBestDefenderSC(pLoopUnit, True)
            if isNotValid(pTargetUnit):
                pCannotAttackUnits.append(pLoopUnit)
            else:
                tTargetID = (pTargetUnit.getOwner(), pTargetUnit.getID())
                if pTargetAttackers.has_key(tTargetID):
                    pTargetAttackers[tTargetID].append(pLoopUnit)
                else:
                    pTargetAttackers[tTargetID] = [pLoopUnit]
        
        # Get all the units that can't attack and give them their own group.
        if len(pCannotAttackUnits) > 0:
            createNewGroup(pCannotAttackUnits)

        # Get all the units that can attack and put them into their own groups.
        # Then, tell that group to destroy attack the target with the targetted destroy attack spell.
        for tTargetInfo in pTargetAttackers.keys():
            pTarget = gc.getPlayer(tTargetInfo[0]).getUnit(tTargetInfo[1])
            pAttackers = pTargetAttackers[tTargetInfo]
            pNewGroup = createNewGroup(pAttackers)
            pNewGroup.pushSpellMission(gc.getInfoTypeForString("SPELL_DESTROY_ATTACK_TARGETTED"), tTargetInfo[0], tTargetInfo[1], -1, True)

        return False




#
# Destroy Attack Targetted
#
class DestroyAttackTargetted(TargettedAttackBase):
    def __init__(self):
        TargettedAttackBase.__init__(self)

    def startSpellResultsOnTarget(self, pCastingGroup, pTarget):
        if isNotValid(pTarget):
            return False

        if pTarget.isDead():
            return False

        # Continue the mission for the remaining units.
        return True

    def continueSpellResultsOnTarget(self, pCastingGroup, pTargetUnit):
        if isNotValid(pTargetUnit):
            return False

        if pTargetUnit.isDead():
            return False

        pUnitsCannotAttack = []
        pNeedToMoveUnits = []
        
        for pLoopUnit in pCastingGroup.getUnits():
            if not pLoopUnit.canSCAttackUnit(pTargetUnit, True):
                pUnitsCannotAttack.append(pLoopUnit)
            elif not pLoopUnit.isUnitWithinSCAttackRange(pTargetUnit):
                pNeedToMoveUnits.append(pLoopUnit)
        del pLoopUnit


        
        iNumUnitsLeftToAttack = pCastingGroup.getNumUnits()

        iNumUnitsLeavingCannotAttack = len(pUnitsCannotAttack)
        if iNumUnitsLeavingCannotAttack == iNumUnitsLeftToAttack:
            return False
        else:
            createNewGroup(pUnitsCannotAttack)
            iNumUnitsLeftToAttack -= iNumUnitsLeavingCannotAttack
        
        iNumUnitsLeavingToMove = len(pNeedToMoveUnits)
        if iNumUnitsLeavingToMove == iNumUnitsLeftToAttack:
            # Move the entire stack
            iNumMoves = pCastingGroup.generatePathReturnModified(pCastingGroup.plot(), pTargetUnit.plot(), -1, True)
            if iNumMoves <= 0:
                # Can't find path to target.
                return False
            else:
                pFirstPlot = pCastingGroup.getPathFirstPlot()
                pCastingGroup.pushMoveToMission(pFirstPlot.getX(), pFirstPlot.getY())
                pCastingGroup.pushSpellMission(self.getSpellNumber(), pTargetUnit.getOwner(), pTargetUnit.getID(), -1, True)
                return False
        else:
            # Create a new group for these units and push this mission.
            if iNumUnitsLeavingToMove > 0:
                pNewGroup = createNewGroup(pNeedToMoveUnits)
                pNewGroup.pushSpellMission(self.getSpellNumber(), pTargetUnit.getOwner(), pTargetUnit.getID(), -1, False)


        # With the final units, do an attack:
        for pLoopUnit in pCastingGroup.getUnits():
            Combat.turnAttackTargetted(pLoopUnit, pTargetUnit)
            pLoopUnit.finishMoves()
            if pTargetUnit.isDead():
                return False
            

        return True


#
# Single Attack (Lurker only)
#
import math
class SingleAttackLurker(LurkerAttackBase):
    def __init__(self):
        LurkerAttackBase.__init__(self)

    def startSpellResultsOnTarget(self, pCastingGroup, pTargetPlot):
        bCombat = False

        pAttackPlots = getLurkerAttackPlotValues(pCastingGroup.plot(), pTargetPlot, gc.getUnitInfo(self.m_iLurkerType).getGroundRange())
        pCastingPlot = pCastingGroup.plot()
        for pUnit in pCastingGroup.getUnits():
            for pPlot, iValue in pAttackPlots:
                Combat.singleLurkerAttack(pUnit, pPlot)

            pUnit.changeAttacks(1) # Cost them one attack
            pUnit.changeMoves(gc.getMOVE_DENOMINATOR()) # Cost them one move
            pUnit.forceCooldown()


            pStartPoint = pCastingPlot.getPoint()
            pFinishPoint = pTargetPlot.getPoint()
            fX0 = pStartPoint.x
            fY0 = pStartPoint.y
            fX1 = pFinishPoint.x
            fY1 = pFinishPoint.y

            fHyp1 = math.sqrt(math.pow(fX1 - fX0, 2.0) + math.pow(fY1 - fY0, 2.0))
            iRange = gc.getUnitInfo(self.m_iLurkerType).getGroundRange()
            fHypz = float(iRange) * gc.getDefineFLOAT("PLOT_SIZE")

            fXz = ( ( (fX1 - fX0) * fHypz) / fHyp1 ) / float(iRange)
            fYz = ( ( (fY1 - fY0) * fHypz) / fHyp1 ) / float(iRange)

            print fXz
            print fYz

            pPoint = NiPoint3()
            pPoint.z = 90.0
            for iEffect in range(iRange + 1):
                print iEffect
                pPoint.x = fX0 + ( fXz * float(iEffect) )
                pPoint.y = fY0 + ( fYz * float(iEffect) )

                print pPoint.x, pPoint.y
                CyEngine().triggerEffect(40, pPoint)

        return False

#
# Turn Attack
#
# GERIKESTODO: make this work
class TurnAttackLurker(LurkerAttackBase):
    def __init__(self):
        LurkerAttackBase.__init__(self)

    def startSpellResultsOnTarget(self, pCastingGroup, pTargetPlot):
        bCombat = False

        pAttackPlots = getLurkerAttackPlotValues(pCastingGroup.plot(), pTargetPlot, gc.getUnitInfo(self.m_iLurkerType).getGroundRange())
        pCastingPlot = pCastingGroup.plot()
        for pUnit in pCastingGroup.getUnits():
            while pUnit.hasMoreAttacksLeft():
                for pPlot, iValue in pAttackPlots:
                    Combat.singleLurkerAttack(pUnit, pPlot)

                pUnit.changeAttacks(1) # Cost them one attack
                pUnit.changeMoves(gc.getMOVE_DENOMINATOR()) # Cost them one move
                pUnit.forceCooldown()


                pStartPoint = pCastingPlot.getPoint()
                pFinishPoint = pTargetPlot.getPoint()
                fX0 = pStartPoint.x
                fY0 = pStartPoint.y
                fX1 = pFinishPoint.x
                fY1 = pFinishPoint.y

                fHyp1 = math.sqrt(math.pow(fX1 - fX0, 2.0) + math.pow(fY1 - fY0, 2.0))
                iRange = gc.getUnitInfo(self.m_iLurkerType).getGroundRange()
                fHypz = float(iRange) * gc.getDefineFLOAT("PLOT_SIZE")

                fXz = ( ( (fX1 - fX0) * fHypz) / fHyp1 ) / float(iRange)
                fYz = ( ( (fY1 - fY0) * fHypz) / fHyp1 ) / float(iRange)

                print fXz
                print fYz

                pPoint = NiPoint3()
                pPoint.z = 90.0
                for iEffect in range(iRange + 1):
                    print iEffect
                    pPoint.x = fX0 + ( fXz * float(iEffect) )
                    pPoint.y = fY0 + ( fYz * float(iEffect) )

                    print pPoint.x, pPoint.y
                    CyEngine().triggerEffect(40, pPoint)

        return False

        
            
            
